package com.core.snowflake.core;



import com.core.snowflake.dto.SnowFlakeSyncDataDto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;

/**
 * 雪花算法
 * 2022-3-11 改进支持自定义配置 苏斌
 * 2023-3-30 改进增加redis直读模式
 */
public class SnowFlakeUtil {

    //#region 系统运行参数
    Logger log  = LoggerFactory.getLogger("snowFlakeUnit");

    private final static long twepoch = 1647176362311L;//12888349746579L;

    /**
     * 是否已经初始化
     */
    private boolean isInt=false;
    /**
     * 是否启用毫秒
     */
    private boolean isTimeMillis = true;

    /**
     * 是否同步数据中心模式
     * false 传入ID永久固定 ，
     * true 需要定期刷新数据中心同步ID(包含datacenter模式和redis模式) ， 超时禁止生成新ID
     */
    private boolean isSync = false;

    /**
     * 数据中心最后同步时间 (毫秒 / 秒 ）
     */
    private long lastSyncTime = 0L;

    /**
     * 客户端标签(唯一标识)
     * todo 目前业务限制无用，暂时用来记录客户端IP
     */
    private String syncTag = "";

    public String getSyncTag() {
        return syncTag;
    }


    public void setSyncTag(String syncTag) {
        this.syncTag = syncTag;
    }

    /**
     * 同步数据中心，最大时间间隔 (单位 毫秒 / 秒)
     * 超过8分钟未同步（数据中心保持10分钟有效锁定），禁用抛出异常
     * 每2分钟同步一次，8分钟内实际可尝试4次
     */
    private long syncMaxTime = 0L;

    /**
     * 同步模式下: 同步间隔
     */
    //private long syncPerTime=0L;

    /**
     * 时间回拨最大延迟等待次数
     */
    private int waitloop = 5;

    /**
     * 时间回拨最大延迟每次等待时间长度
     * 注意配置isTimeMillis是毫秒，此值是毫秒，如果配置是秒，此值是秒
     */
    private int waitPerValue = 1;

    //#endregion

    //#region 构造函数


    /**
     * 单机版本 手动配置
     *
     * @param _workerId         机器唯一ID
     * @param _datacenterId     数据中心唯一ID
     * @param _isTimeMillis     时间单位毫秒
     * @param _sequenceBits     自增标识位数
     * @param _workerIdBits     机器标识位数
     * @param _datacenterIdBits 数据中心标识位数
     */
    public SnowFlakeUtil(long _workerId, long _datacenterId, boolean _isTimeMillis, long _sequenceBits, long _workerIdBits, long _datacenterIdBits) {
        intSystem(false, _workerId, _datacenterId, _isTimeMillis, _sequenceBits, _workerIdBits, _datacenterIdBits);
        log.info("雪花ID中心单机模式,手动配置初始化完成！ workerId:"+_workerId+" datacenterId:"+_datacenterId);
    }

    /**
     * 单机版本 手动配置
     * @param _workerId
     * @param _datacenterId
     */
    public SnowFlakeUtil(long _workerId, long _datacenterId) {
        //5 ->32 , 6->64
        intSystem(false, _workerId, _datacenterId, true, 12, 5, 5);
        log.info("雪花ID中心单机模式,手动配置初始化完成！ workerId:"+_workerId+" datacenterId:"+_datacenterId);
    }

    /**
     * 构造函数
     * @param _isSync true 数据中心模式  false 单机版模式
     */
    public SnowFlakeUtil(boolean _isSync) {
        intSystem(_isSync);
    }

    public SnowFlakeUtil()
    {
        intSystem(false);
    }
    //#endregion

    /**
     *  数据中心模式->初始化ID + 同步数据
     */
    public void syncSnowFlake(SnowFlakeSyncDataDto dto) {
        //同步出错不抛出异常，继续使用，直到同步时限timeout
        if (dto == null || (!dto.getCodeState().equals("add") && !dto.getCodeState().equals("synchronization")))
            return;
        if (!isInt) {
            //重要，如果初始化同步中心模式进来，在构造函数时候（并未没有getWorkerId，getDatacenterId） ，snutil并未初始化ID设置，需要在这里进行一次初始化
            intSystem(true, dto.getWorkerId(), dto.getDatacenterId(), true, 12, 5, 5);
            //log.info("雪花ID中心同步成功。。。。");
        }
        //log.debug("雪花ID数据中心同步成功。。。。");
        if (isTimeMillis) {
            lastSyncTime = dto.getLastSyncTime();
            if (syncMaxTime != dto.getSyncMaxTime())
                syncMaxTime = dto.getSyncMaxTime();
        } else {
            lastSyncTime = dto.getLastSyncTime() / 1000;
            if (syncMaxTime != dto.getSyncMaxTime() / 1000)
                syncMaxTime = dto.getSyncMaxTime() / 1000;
        }
        if (dto.getCodeState().equals("add")) {
            syncTag = dto.getSyncTag();
            workerId = dto.getWorkerId();
            datacenterId = dto.getDatacenterId();
        }
    }



    /**
     * 系统初始化
     * @param _isSync
     */
    private void intSystem(boolean _isSync)
    {
        if (!_isSync) {
            intSystem(false, 1, 1, true, 12L, 5L, 5L);
            log.info("雪花ID中心单机版,auto初始化完成！ workerId:1 datacenterId:1");
        } else {
            //数据中心模式，什么也不做，有同步接口进行初始化操作
            log.info("雪花ID中心同步版，模式启动，等待同步获取中。。。。");
        }
    }

    /**
     * 系统初始化
     * @param _isSync           是否是同步数据中心模式
     * @param _isTimeMillis     是否单位为毫秒
     * @param _workerId         机器唯一ID
     * @param _datacenterId     数据中心唯一ID
     * @param _sequenceBits     顺序位数
     * @param _workerIdBits     机器ID位数
     * @param _datacenterIdBits 数据中心位数
     */
    private void intSystem(boolean _isSync,  long _workerId, long _datacenterId, boolean _isTimeMillis, long _sequenceBits, long _workerIdBits, long _datacenterIdBits) {
        //-------------时间单位 秒/毫秒
        isTimeMillis = _isTimeMillis;
        //时间回拨等待
        if (isTimeMillis) {
            //毫秒
            waitloop = 50;
            waitPerValue = 100;
        } else {
            //秒
            waitloop = 5;
            waitPerValue = 1000;
        }
        //----------是否开启数据中心同步---------
        isSync = _isSync;
        //----------唯一ID-------------
        this.workerId = _workerId;
        this.datacenterId = _datacenterId;
        //--------雪花配置-------------
        //#region 雪花配置
        sequenceBits = _sequenceBits;
        workerIdBits = _workerIdBits;
        datacenterIdBits = _datacenterIdBits;
        //---------重新计算位移，重要，如果这里不执行，一旦默认长度和位数变化了，就会和默认值位移有冲突
        workerIdShift = sequenceBits;
        // 数据中心ID左移17位 = a+b
        datacenterIdShift = sequenceBits + workerIdBits;
        // 时间毫秒左移22位    a+b+c
        timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
        //序号mask码  sequence掩码，确保sequnce不会超出上限
        sequenceMask = -1L ^ (-1L << sequenceBits);//4095
        //服务器IDmask码
        workerMask = -1L ^ (-1L << workerIdBits);//32
        //数据中心IDmask码
        datacenterMask = -1L ^ (-1L << datacenterIdBits);//32
        //时间掩码
        timeMask = -1L ^ (-1L << (64-1-timestampLeftShift) );//41 毫秒 69年 40 毫秒 34年
        //#endregion
        //-------------------------------


        //避免编码超出最大值
        if (workerId > workerMask || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", workerId));
        }
        if (datacenterId > datacenterMask || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", datacenterId));
        }

        //初始化完成
        isInt=true;
    }



    //#region 原始雪花内容

    //#region 雪花ID具体内容存储在二进制中位置
    //-------------------------具体内容存储在二进制中位置---------------------

    // 41bit时间戳 +  数据中心c(5) +机器标识b(5) + 单毫秒内序号d(12)
    //工作机器id a =  b+c  = 10L  ( 32 * 32= 1024个工作机器ID）
    //时间戳（毫秒）
    //private final static long timesbits = 64L;
    //b 机器标识位数=》 0-31个整数
    private static long workerIdBits = 5L;
    //c 数据中心标识位数 => 0-31个整数
    private static long datacenterIdBits = 5L;
    //d 毫秒内自增位数  序列号，用来记录同毫秒内产生的不同id==>可以表示的最大正整数是212−1=4095==>来表示同一机器ID同一时间截（毫秒)内产生的4095个ID序号
    private static long sequenceBits = 12L;
    // 机器ID偏左移12位 =d


    //-----------------------------------------------------------------

    /**
     * 上次更新时间戳
     */
    private static long lastTimestamp = -1L;

    //------------------------------runtime值---------------------------------------
    //runtime值  ==》 （当前时间戳-参考时间戳）+ 数据中心ID+服务中心ID+序号ID
    //（当前时间戳-参考时间戳） 的意思是，以参考时间戳起 69年内的 时间戳差值够 存41bit时间戳
    //序列ID 单毫秒中 -> (0-4095)
    /**
     * 序列ID
     * 5位(0-31)
     */
    private long sequence = 1L;

    /**
     * 服务器ID
     * 5位(0-31)
     * 默认-1直接使用会出错，需要初始化
     */
    private long workerId = -1L;

    /**
     * 服务器ID
     * @return
     */
    public long getWorkerId() {
        return workerId;
    }



    /**
     * 数据中心ID
     * 5位(0-31)
     * 默认-1直接使用会出错，需要初始化
     */
    private long datacenterId = -1L;

    /**
     *      * 数据中心ID
     * @return
     */
    public long getDatacenterId() {
        return datacenterId;
    }


    //机器中心ID位移
    private static long workerIdShift = sequenceBits;
    // 数据中心ID左移17位 = a+b
    private static long datacenterIdShift = sequenceBits + workerIdBits;
    // 时间毫秒左移22位    a+b+c
    private static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;



    //序号mask码  sequence掩码，确保sequnce不会超出上限
    private static long sequenceMask = -1L ^ (-1L << sequenceBits);//4095
    //服务器IDmask码
    private static long workerMask = -1L ^ (-1L << workerIdBits);//32
    //数据中心IDmask码
    private static long datacenterMask = -1L ^ (-1L << datacenterIdBits);//32
    //todo 需要检查固定变量 41
    //时间掩码
    private static long timeMask = -1L ^ (-1L << (64-1-timestampLeftShift) );//69年
    //-------------------------------------------------------------------------

    //#endregion


    //#region 雪花具体算法


    public synchronized long nextId() throws Exception {

        if(!isInt)
            throw new Exception("雪花算法未初始化完成，不能生产新ID");

        //获取时间戳
        long timestamp = timeGen();

        //如果是数据中心模式，判断时间是否过期，过期直接抛出异常
        if (isSync  && timestamp - lastSyncTime >= syncMaxTime)
            throw new Exception("雪花算法失去和数据中心心跳，不能生产新ID,请等待数据中心同步");


        //如果时间戳小于上次时间戳则报错,此处用于检测时间回拨，如果回拨抛出异常
        //如果碰到时间同步，时间回拨,延迟等待,还是过期则抛出异常(毫秒级别的时间回拨，等待3秒）
        int loops = 0;
        while (timestamp < lastTimestamp && loops <= waitloop) {
            loops++;
            Thread.sleep(waitPerValue);
            timestamp = timeGen();
        }


        if (timestamp < lastTimestamp) {
            try {
                throw new Exception("产生了时间回拨.  Refusing to generate id for " + (lastTimestamp - timestamp) + " ");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        //如果时间戳与上次时间戳相同
        if (lastTimestamp == timestamp) {//毫秒数相同情况下
            // 当前毫秒内，则+1，与sequenceMask确保sequence不会超出上限
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {//超过4095，强制等待下一毫秒
                // 当前毫秒内计数满了，则等待下一毫秒
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0;//毫秒数不同,序号重新置0
        }
        lastTimestamp = timestamp;
        // ID偏移组合生成最终的ID，并返回ID
        long nextId = ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence;
        return nextId;
    }

    /**
     * 再次获取时间戳直到获取的时间戳与现有的不同
     * 不断死循环，直到下一毫秒的变化返回新值
     *
     * @param lastTimestamp
     * @return 下一个时间戳
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = this.timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = this.timeGen();
        }
        return timestamp;
    }

    /**
     * 当前系统时间
     *
     * @return
     */
    private long timeGen() {
        //修改秒为时间单位
        //调整，时间只对秒灵敏，成功回避了服务器间几百毫秒的时间误差引起的时间回拨问题
        if (isTimeMillis)
            return System.currentTimeMillis();
        else
            return System.currentTimeMillis() / 1000;

    }

    /**
     * 获取机器编码
     *
     * @return
     */
    private long getMachineNum() {
        long machinePiece;
        StringBuilder sb = new StringBuilder();
        Enumeration<NetworkInterface> e = null;
        try {
            e = NetworkInterface.getNetworkInterfaces();
        } catch (SocketException e1) {
            e1.printStackTrace();
        }
        while (e.hasMoreElements()) {
            NetworkInterface ni = e.nextElement();
            sb.append(ni.toString());
        }
        machinePiece = sb.toString().hashCode();
        return machinePiece;
    }

    //#endregion

    //#endregion
}

/**
 * run: 41+5+5+12
 *
 * System.out.println(SnowFlakeUtil.nextId());
 * System.out.println(SnowFlakeUtil.nextId());
 * System.out.println(SnowFlakeUtil.nextId());
 * Thread.sleep(1000L);
 * System.out.println("下一秒");
 * System.out.println(SnowFlakeUtil.nextId());
 * System.out.println(SnowFlakeUtil.nextId());
 * System.out.println(SnowFlakeUtil.nextId());
 *
 * 样例分析
 * 8189824485796380672->  011100011010100000011001111110100111000010   11000 01000 000000000000
 * 8189824485796380673->  011100011010100000011001111110100111000010   11000 01000 000000000001
 * 8189824485796380674->  011100011010100000011001111110100111000010   11000 01000 000000000010
 * 下一秒
 * 8189824489990684672->  011100011010100000011001111110110110101010   11000 01000 000000000000
 * 8189824489990684673->  011100011010100000011001111110110110101010   11000 01000 000000000001
 * 8189824489990684674->  011100011010100000011001111110110110101010   11000 01000 000000000010
 *
 * //-----------------提取时间字段分析
 *
 * 011100011010100000011001111110100111000010
 * 011100011010100000011001111110110110101010
 * 1952606317(秒）994（毫秒）
 * 1952606316(秒）994（毫秒）
 */